// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;

namespace SnapObjects.Data
{
    internal static class ValueConvert
    {
        public static bool TryConvert<TValue>(object value, out TValue result)
        {
            result = default;

            var success = TryConvert(value, typeof(TValue), out var ret);

            if (success)
            {
                result = (TValue)ret;
            }

            return success;
        }

        public static bool TryConvert(object value, Type type, out object result)
        {
            result = null;

            if (value == null)
            {
                return Nullable.GetUnderlyingType(type) != null;
            }

            var targetType = Nullable.GetUnderlyingType(type) ?? type;

            var targetTypeCode = Type.GetTypeCode(targetType);

            var valueType = Nullable.GetUnderlyingType(value.GetType()) ?? value.GetType();

            var valueTypeCode = Type.GetTypeCode(valueType);

            var canConvert = false;

            if (targetTypeCode == valueTypeCode)
            {
                result = value;

                canConvert = true;
            }
            else if (targetTypeCode > TypeCode.Char && valueTypeCode < targetTypeCode)
            {
                if (targetTypeCode == TypeCode.Decimal)
                {
                    if (valueTypeCode != TypeCode.Double && valueTypeCode != TypeCode.Single)
                    {
                        result = System.Convert.ChangeType(
                            value, targetTypeCode, CultureInfo.InvariantCulture);

                        canConvert = true;
                    }
                }
                else if (targetTypeCode == TypeCode.String && valueTypeCode == TypeCode.Object)
                {
                    result = ToString(value);

                    canConvert = true;
                }
                else if (!IsUnsignedType(targetTypeCode) || (IsUnsignedType(targetTypeCode) && IsUnsignedType(valueTypeCode)))
                {
                    result = System.Convert.ChangeType(
                        value, targetTypeCode, CultureInfo.InvariantCulture);

                    canConvert = true;
                }
            }

            return canConvert;
        }

        public static TValue Convert<TValue>(object value)
        {
            return (TValue)Convert(value, typeof(TValue));
        }

        public static object Convert(object value, Type type)
        {
            if (value == null || value.GetType() == type)
            {
                return value;
            }

            var targetType = Nullable.GetUnderlyingType(type) ?? type;

            if (targetType == typeof(bool))
            {
                return ToBoolean(value);
            }
            else if (targetType == typeof(byte))
            {
                return ToByte(value);
            }
            else if (targetType == typeof(short))
            {
                return ToShort(value);
            }
            else if (targetType == typeof(int))
            {
                return ToInt(value);
            }
            else if (targetType == typeof(long))
            {
                return ToLong(value);
            }
            else if (targetType == typeof(float))
            {
                return ToFloat(value);
            }
            else if (targetType == typeof(double))
            {
                return ToDouble(value);
            }
            else if (targetType == typeof(decimal))
            {
                return ToDecimal(value);
            }
            else if (targetType == typeof(DateTime))
            {
                return ToDateTime(value);
            }
            else if (targetType == typeof(TimeSpan))
            {
                return ToTimeSpan(value);
            }
            else if (targetType == typeof(string))
            {
                return ToString(value);
            }
            else if (targetType.IsEnum)
            {
                return ToEnum(value, targetType);
            }
            else if (targetType == typeof(DateTimeOffset))
            {
                return ToDateTimeOffset(value);
            }
            else if (targetType == typeof(Guid))
            {
                return ToGuid(value);
            }
            else if (targetType == typeof(byte[]))
            {
                return ToBytes(value);
            }
            else
            {
                return System.Convert.ChangeType(value, targetType, CultureInfo.InvariantCulture);
            }
        }

        public static string ToString(object value)
        {
            if (value == null)
            {
                return null;
            }

            if (value is string str)
            {
                return str;
            }

            return System.Convert.ToString(value, CultureInfo.InvariantCulture);
        }

        private static bool ToBoolean(object value)
        {
            if (value is bool b)
            {
                return b;
            }

            if (value is string str)
            {
                if (Boolean.TrueString.Equals(str, StringComparison.OrdinalIgnoreCase))
                {
                    return true;
                }
                else if (Boolean.FalseString.Equals(str, StringComparison.OrdinalIgnoreCase))
                {
                    return false;
                }
                else if (Int32.TryParse(str, out var b1))
                {
                    return (bool)System.Convert.ChangeType(b1, typeof(bool));
                }
            }

            return System.Convert.ToBoolean(value, CultureInfo.InvariantCulture);
        }

        private static byte ToByte(object value)
        {
            if (value is byte b)
            {
                return b;
            }

            if (value is string str && Byte.TryParse(str, out var v))
            {
                return v;
            }

            return System.Convert.ToByte(value, CultureInfo.InvariantCulture);
        }

        private static short ToShort(object value)
        {
            if (value is short s)
            {
                return s;
            }

            if (value is string str && Int16.TryParse(str, out var v))
            {
                return v;
            }

            return System.Convert.ToInt16(value, CultureInfo.InvariantCulture);
        }

        private static int ToInt(object value)
        {
            if (value is int i)
            {
                return i;
            }

            if (value is string str && Int32.TryParse(str, out var v))
            {
                return v;
            }

            return System.Convert.ToInt32(value, CultureInfo.InvariantCulture);
        }

        private static long ToLong(object value)
        {
            if (value is long l)
            {
                return l;
            }

            if (value is string str && Int64.TryParse(str, out var v))
            {
                return v;
            }

            return System.Convert.ToInt64(value, CultureInfo.InvariantCulture);
        }

        private static float ToFloat(object value)
        {
            if (value is float f)
            {
                return f;
            }

            var numStyles = NumberStyles.Float | NumberStyles.AllowThousands;

            if (value is string str &&
                Single.TryParse(str, numStyles, CultureInfo.InvariantCulture, out var v))
            {
                return v;
            }

            return System.Convert.ToSingle(value, CultureInfo.InvariantCulture);
        }

        private static double ToDouble(object value)
        {
            if (value is double d)
            {
                return d;
            }

            var numStyles = NumberStyles.Float | NumberStyles.AllowThousands;

            if (value is string str &&
                Double.TryParse(str, numStyles, CultureInfo.InvariantCulture, out var v))
            {
                return v;
            }

            return System.Convert.ToDouble(value, CultureInfo.InvariantCulture);
        }

        private static decimal ToDecimal(object value)
        {
            if (value is decimal d)
            {
                return d;
            }

            if (value is string str &&
                Decimal.TryParse(str, NumberStyles.Number, CultureInfo.InvariantCulture, out var v))
            {
                return v;
            }

            return System.Convert.ToDecimal(value, CultureInfo.InvariantCulture);
        }

        private static DateTime ToDateTime(object value)
        {
            if (value is DateTime dt)
            {
                return dt;
            }

            if (value is string str && DateTime.TryParse(str, out var v))
            {
                return v;
            }

            if (value is long i)
            {
                return new DateTime(i);
            }

            return System.Convert.ToDateTime(value, CultureInfo.InvariantCulture);
        }

        private static object ToEnum(object value, Type type)
        {
            var str = ToString(value);

            return Enum.Parse(type, str, true);
        }

        private static DateTimeOffset ToDateTimeOffset(object value)
        {
            if (value is DateTimeOffset dt)
            {
                return dt;
            }

            var str = ToString(value);

            if (DateTimeOffset.TryParse(str, out var r))
            {
                return r;
            }

            return (DateTimeOffset)System.Convert.ChangeType(
                value, typeof(DateTimeOffset), CultureInfo.InvariantCulture);
        }

        private static TimeSpan ToTimeSpan(object value)
        {
            if (value is TimeSpan ts)
            {
                return ts;
            }

            var str = ToString(value);

            if (TimeSpan.TryParse(str, out var r))
            {
                return r;
            }

            return (TimeSpan)System.Convert.ChangeType(
                value, typeof(TimeSpan), CultureInfo.InvariantCulture);
        }

        private static Guid ToGuid(object value)
        {
            if (value is Guid g)
            {
                return g;
            }

            var str = ToString(value);

            if (Guid.TryParse(str, out var r))
            {
                return r;
            }

            return (Guid)System.Convert.ChangeType(value, typeof(Guid), CultureInfo.InvariantCulture);
        }

        private static byte[] ToBytes(object value)
        {
            if (value is string str)
            {
                return System.Convert.FromBase64String(str);
            }

            return (byte[])System.Convert.ChangeType(
                value, typeof(byte[]), CultureInfo.InvariantCulture);
        }

        private static bool IsUnsignedType(TypeCode typeCode)
        {
            switch (typeCode)
            {
                case TypeCode.Byte:
                case TypeCode.UInt16:
                case TypeCode.UInt32:
                case TypeCode.UInt64:
                    return true;
            }

            return false;
        }
    }
}
